package com.wanakiko.impldetails.commons;

import com.google.common.collect.Maps;
import com.wanakiko.impldetails.mapper.UserInfoMapper;
import org.apache.ibatis.annotations.Select;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Map;

public class WhatIsMybatis {

    public static void main(String[] args) {
        UserInfoMapper userInfoMapper = (UserInfoMapper) Proxy.newProxyInstance(WhatIsMybatis.class.getClassLoader(),
                new Class<?>[]{UserInfoMapper.class}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Select annotation = method.getAnnotation(Select.class);
                        /* 传入X个参数则遍历X次，参数拼接只取最后遍历的值 */
                        Map<String, Object> nameArgsMap = buildMethodParamsNameMap(method, args);
                        if (annotation != null) {
                            System.out.println("方法注解(数据库ID和SQL)：" + annotation);
                            System.out.println("获取方法注解中的SQL：" + Arrays.toString(annotation.value()));
                            String[] value = annotation.value();
                            String sql = value[0];
                            /* 解析并重新赋值原来SQL的格式 */
                            sql = parseSql(sql, nameArgsMap);
                            System.out.println("经方法解析后的SQL：" + sql);
                            System.out.println("获取方法的返回值类型：" + method.getReturnType());
                            System.out.println("获取方法的通用返回类型(集合类型)：" + method.getGenericReturnType());
                        }
                        return null;
                    }
                });
        /* 传入X个参数则遍历X次，参数拼接只取最后遍历的值 */
        userInfoMapper.loadUserInfoAsList("%wanakiko");
    }

    /**
     * 解析SQL
     *
     * @param sql      SQL
     * @param paramMap 参数映射
     * @return 解析结果
     */
    public static String parseSql(String sql, Map<String, Object> paramMap) {
        StringBuilder sqlBuilder = new StringBuilder(); /* 准备线程不安全的StringBuilder */
        int length = sql.length(); /* 确定SQL的长度 */
        for (int i = 0; i < length; i++) {
            char c = sql.charAt(i);
            if (c == '#') { /* 如果找到SQL中的井号则进入判断 */
                int nextIndex = i + 1;
                char nextChar = sql.charAt(nextIndex);
                if (nextChar != '{') { /* SQL的井号之后如果不是左侧花括号 { 则代表定义的SQL有误 */
                    throw new RuntimeException(String.format("#{\nsql: %s\nindex: %d", sqlBuilder.toString(), nextIndex));
                }
                StringBuilder paramBuilder = new StringBuilder();
                /* 找到并解析左侧花括号 { 之后的SQL参数 */
                i = parseSqlParams(paramBuilder, sql, nextIndex);
                String paramName = paramBuilder.toString();
                /* 反射获取@Select注解中的SQL参数值 */
                Object paramValue = paramMap.get(paramName);
                if (paramValue == null) { /* 找不到相应的参数值时抛出运行时异常 */
                    throw new RuntimeException(String.format("找不到参数值: %s", paramName));
                }
                sqlBuilder.append(paramValue.toString());
                continue;
            }
            sqlBuilder.append(c);
        }
        return sqlBuilder.toString();
    }

    private static int parseSqlParams(StringBuilder paramBuilder, String sql, int nextIndex) {
        nextIndex++; /* 跳过SQL中左侧花括号 { 的解析 */
        for (; nextIndex < sql.length(); nextIndex++) {
            char c = sql.charAt(nextIndex);
            if (c != '}') { /* 循环未找到右侧花括号 } 时继续追加参数 */
                paramBuilder.append(c);
                continue;
            }
            if (c == '}') { /* 循环已找到右侧花括号 } 则立即停止循环并返回最终的位置 */
                return nextIndex;
            }
            /* 向@Select注解中传入非法SQL时抛出的运行时异常 */
            throw new RuntimeException(String.format("缺少右括号\nindex: %d", nextIndex));
        }
        return 0;
    }

    /**
     * 构建方法的参数名称映射
     *
     * @param method 方法
     * @param args   参数
     * @return 映射结果
     */
    public static Map<String, Object> buildMethodParamsNameMap(Method method, Object[] args) {
        Map<String, Object> paramMap = Maps.newConcurrentMap();
        Parameter[] parameters = method.getParameters();
        int[] index = {0};
        /* stream()串行不存在线程安全问题，而parallelStream()并行则需要搭配ConcurrentHashMap保证线程安全 */
        Arrays.asList(parameters).parallelStream().forEach(parameter ->
                paramMap.put(parameter.getName(), args[index[0]++]));
        System.out.println("方法的参数及其对应的值(仅取末尾参数)：" + paramMap.toString());
        return paramMap;
    }

}
